<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="root">
    <div>{{name}}</div>
    <div>{{age}}</div>
    <div>hello2</div>
    <ul>
      <li>1</li>
      <li>2</li>
      <li>3</li>
    </ul>
  </div>
</body>
  <script>
    class Vnode {
      constructor(tag,data,value,type){
        this.tag = tag&&tag.toLowerCase();
        this.data = data;// 节点属性
        this.value = value;//文本内容
        this.type = type;
        this.children =[]
      }
      appendChild(vNode){
        this.children.push(vNode)
      }
    }
    
    function getVnode(node){
      let nodeType = node.nodeType;
      let _vnode = null;
      if(nodeType===1){
        let targName = node.nodeName;
        let attrs = node.attributes // attributes 返回是个伪数组
        let _attrObject = {}
        for(let i = 0;i<attrs.length;i++){ // attrs[i] 是属性节点
          _attrObject[attrs[i].nodeName] =attrs[i].nodeValue
        }
        _vnode = new Vnode(targName,_attrObject,undefined,nodeType)
        let childNodes = node.childNodes
        for(let i = 0 ;i<childNodes.length;i++){
          _vnode.appendChild(getVnode(childNodes[i]))
        }
      } else if(nodeType ===3){
        _vnode = new Vnode(undefined,undefined,node.nodeValue,nodeType)
      }
      return _vnode
    }
    
    let kk = /\{\{(.+?)\}\}/g

    function getValueByPath (data, path) {
      let pathArr = path.split('.')
      let res = data;
      let prop;
      while (prop = pathArr.shift()) {
        res = res[prop]
      }
      return res
    }
    
    function paseVnode(vnode){
        let type = vnode.type
        let _node =null;
        if(type===3){
          return document.createTextNode(vnode.value)
        } else if(type ===1){
          _node = document.createElement(vnode.tag);
          //属性
          let data= vnode.data;
          Object.keys(data).forEach(item =>{
            let attrName = item;
            let attrValue = data[item];
            _node.setAttribute(attrName,attrValue);
          })
          //子元素
          let children = vnode.children;
          children.forEach(subvnode =>{
            _node.appendChild(paseVnode(subvnode))
          })

          return _node
        }
      }
    //  将带有坑的vnode 与data 生成新的vnode
    function combine (vnode,data) {
      let _tag = vnode.tag
      let _type = vnode.type;
      let _data = vnode.data;
      let _value = vnode.value;
      let _children = vnode.children;
      let _vnode =null;

      if(_type ===3){
        // 对文本处理
        _value = _value.replace(kk,function(_,g){
          return getValueByPath(data,g.trim())
        })
        _vnode = new Vnode(_tag,_data,_value,_type)
      } else if(_type===1){
        _vnode = new Vnode(_tag,_data,_value,_type);
        _children.forEach(_subVnode => _vnode.appendChild(combine(_subVnode,data)))
      }
      return _vnode
    }
      // 本节实现缓存 抽象语法树 ast
      // vue 使用了二次提交  
      // 1页面中的dom 与vnode 是一一对应的    引用类型
      // 2先ast 生成 和数据  render生成 新的 vnode
      // 3旧vonde 和新的vonde 比较diff  更新updata
    function Yvue (options) {
      this._options=options;
      this._data = options.data;
      this._elm = document.querySelector(options.el);
      
      this._template = this._elm;
      this._parent = this._elm.parentNode;
      reactify(this._data,this )// 传入vue 实例演示 刷新模板

      this.initData()// 将data 进行响应式转化 进行代理
      this.mount();// 挂载
    }

    // 取代原来 ractify （将属性转化成响应式）； 方法 ，将对象转化成响应式；
    function observer(obj,propName,vm){
      // 在这里调用 defindeReactive
      // 在这里查看对象的成员，递归
      let keys = Object.keys(obj);
      defineReactive()
    }

    Yvue.prototype.initData =function(){
      // 遍历this._data 成员 进行响应式转化  进行代理
     let keys = Object.keys(this._data);

     for(let i = 0;i<keys.length;i++){
      // 进行响应式转化
      reactify(this._data,this)
     }

     for(let i = 0;i<keys.length;i++){
      // 进行代理
        proxy(this,'_data',keys[i])
      }
    }

    function proxy(target,prop,key){
      Object.defineProperty(target,key,{
          enmuerable:true,
          configurable:true,
          get(){
            return target[prop][key]
          },
          set(newValue){
            target[prop][key] = newValue
          }
        })
    }
    
    Yvue.prototype.mount = function(){

      //  需要提供个render 方法 生成虚拟dome
      this.render = this.createRenderFn()

      this.mountComponent();
    }
   
    Yvue.prototype.mountComponent = function(){
      // 执行mountComponent
      let mount = () =>{
        this.upData(this.render())
      }
      mount.call(this); // 本质交个watcher 来调用
    }

// 这里是生成render 函数目的是缓存抽象语法树 用虚拟dome 模拟
    Yvue.prototype.createRenderFn =function(){
      // 带坑的 生成vnode
      let ast = getVnode(this._template)
      // ast 与data 生成新的vnode   我们用 带坑的 vnode 与data 生成新的vnode
      return function render (){
        let temp = combine(ast,this._data)
        return temp
      }
    } 
// 将虚拟dome  渲染到dome 中 ：deff 算法就在这  
    Yvue.prototype.upData= function(vnode){
        // 简化 , 直接生成html dom 替换到页面中
        let raldom = paseVnode(vnode);
        this._parent.replaceChild(raldom,document.querySelector(this._options.el))
    }

// 响应式化 
    const ARRAY_METHOD=[
      'push',
      'pop',
      'shift',
      'unshft',
      'reverse',
      'srot',
      'splice'
    ]
    let array_methods = Object.create(Array.prototype);

    function reactify(o,vm){
      let keys =Object.keys(o);
      for(let i =0;i<keys.length;i++){
        let key = keys[i];
        let value = o[key];
        // 判断属性 是不是引用类似，判断是不是数组
        // 如果是数组 就要循环数组 ，然后将数组里面的元素响应式化
        if(Array.isArray(value)){
          console.log('sss',value,value.push({name:'ss'}))
          value.__proto__ = array_methods
          for(let j=0;j<value.length;j++){
            reactify(value[j],vm)
          }
        } else {
          // 对象或值类型
          defineReactive.call(vm,o,key,value ,true)
        }
        //  只需要在这里加入代理 （问题：在这里加入代理 会递归）
        // {
        //  data:{name:'jacl',child:{name:'tom'}}
        // }  递归时 child 的name 会覆盖 上一层name
      }
    }

    ARRAY_METHOD.forEach(method =>{
      array_methods[method]=function(){
        // 调用原来的方法
      console.log('调用拦截的方法','method')

      for(let i =0 ;i<arguments.length;i++){
        reactify(arguments[i])
      }

      let res = Array.prototype[method].apply(this,arguments)
        // Array.prototype[method].call(this,...arguments)  报错 arguments不是真数组 而是类数组 扩展运算符不能这样用
        return res
      }
    })

    const defineReactive = function(target,key,value,enmuerable){
      // this 指向vue 实例 因为调用时 call 传入了vue 实例
      // if(typeof value ==='Object' && value !=null && !Array.isArray(value)){
      //   // 非数组的引用类型
      // }
      let that = this
      Object.defineProperty(target,key,{
        configurable:true,
        enumerable:!!enmuerable,
        get(){
          console.log('a')
          return value
        },
        set(newValue){
          console.log('b')
          // 模板刷新 演示
          // 如何获取vue 实例  watchter
          if(typeof newValue === 'object' && newValue != null){
            value = reactify(newValue); // 不安全 有问题 暂时用这个   加个判断 如果是对象类型就 reactify
          } else{
            value = newValue;
          }
          that.mountComponent()
        }
      })
    }

    let app = new Yvue({
      el:'#root',
      data:{
        name:'zhangsang',
        age:20,
        child:{
          name:'zhangmamZ'
        }
      }
  })
  //  代理方法  app.name  与 app._data.name 访问同一个数据源 
 
  </script>
</html>